// Copyright (c) 2008 Matthew Johnson
// Licensed under the MIT License (see license.txt)
package actionml.display {
	import actionml.Global;
	
	import flash.display.DisplayObject;
	import flash.display.FrameLabel;
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.FocusEvent;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.ui.Keyboard;
		
	public class TimelineController {
		public static function initialize() {
			Global.initializeExtension(TimelineController, MovieClip);
		}
		public static var instanceMethods:Object = {
			hasDisplayState: function(s:String) {
				for (var i:uint = 0; i < this.currentLabels.length; i++) {
				    var label:FrameLabel = this.currentLabels[i];
					if (label.name == s) {
						return true;
					}
				}
				return false;
			},
			getDisplayState: function() {
				if (!this.currentLabel) {
					return this.displayState;
				} else {
					return this.currentLabel;
				}
			},
			revertDisplayState:function(stateToRevert:String="", callback:Function=null) {
				var mc:MovieClip = this;
				if (!this.lastDisplayState || (stateToRevert != this.displayState && stateToRevert != "")) {
					return;
				}
				if (!this.visible && this.lastDisplayState != "hidden") {
					this.visible = true;
				}
				var displayState:String = this.displayState;
				this.displayState = this.lastDisplayState;
				this.lastDisplayState = null;
				this.playTransition(displayState, this.displayState, function() {
					mc.gotoAndStop(mc.displayState);
					if (mc.displayState == "hidden") {
						mc.visible = false;
					}
					if (callback != null) {
						callback();
					}
				});
			},
			showNextDisplayState: function(transition:Boolean=false, loop:Boolean=true) {
				var found:Boolean = false;
				for (var i:uint = 0; i < this.currentLabels.length; i++) {
				    var label:FrameLabel = this.currentLabels[i];
					if (label.frame > this.currentFrame) {
						this.showDisplayState(label.name, transition);
						found = true;
						break;
					}
				}
				if (!found && loop) {
					this.showDisplayState(this.currentLabels[0].name, transition);
				}	
			},
			showDisplayState: function(state:String, transition:Boolean=false) {
				if (transition) {
					this.setDisplayState(state);
				} else {
					this.gotoAndStop(state);
				}
			},
			setDisplayState: function(newDisplayState:String, callback:Function=null) { 
				var mc:MovieClip = this;
				this.lastDisplayState = this.getDisplayState() || "";
				if (!this.visible && newDisplayState != "hidden") {
					this.visible = true;
				}
				this.displayState = newDisplayState;
				this.playTransition(this.lastDisplayState, newDisplayState, function() {
					mc.gotoAndStop(newDisplayState);
					if (newDisplayState == "hidden") {
						mc.visible = false;
					}
					if (callback != null) {
						callback();
					}
				});
			},
			playTransition: function(state1:String, state2:String, callbackWhenDone:Function=null) {
				var endFrame:FrameLabel;
				// first try a specifically labelled transition
				var startFrame:FrameLabel = this.getFrameLabelForName(state1 + "_to_" + state2)
				if (startFrame) {
					endFrame = this.getFrameLabelForName("end_" + state1 + "_to_" + state2);
					if (!endFrame) {
						endFrame = this.getFrameLabelForName(state2);
					}
					if (endFrame) {						
						this.playFromFrameToFrame(startFrame.frame, endFrame.frame, callbackWhenDone);
						return;
					}
				// then try directly adjacent labels
				} else if (this.isLabel2ImmediatelyAfterLabel1(state1, state2)) {
					startFrame = this.getFrameLabelForName(state1);
					endFrame = this.getFrameLabelForName(state2);
					this.playFromFrameToFrame(startFrame.frame, endFrame.frame, callbackWhenDone);
					return;
				} else {
					// then try a specifically labeled reverse transition
					startFrame = this.getFrameLabelForName(state2 + "_to_" + state1);
				}
				if (startFrame && !endFrame) {
					endFrame = this.getFrameLabelForName("end_" + state2 + "_to_" + state1);
					if (!endFrame) {
						endFrame = this.getFrameLabelForName(state1);
					}
					if (endFrame) {
						this.playFromFrameToFrame(endFrame.frame, startFrame.frame, callbackWhenDone);
						return;
					}
				// then try directly adjacent labels in reverse
				} else if (this.isLabel2ImmediatelyAfterLabel1(state2, state1)) {
					startFrame = this.getFrameLabelForName(state1);
					endFrame = this.getFrameLabelForName(state2);
					this.playFromFrameToFrame(startFrame.frame, endFrame.frame, callbackWhenDone);
					return;
				} else {
					// no specific or adjacent transition exists
					// play generic out transition followed by generic in transition	
					var mc:MovieClip = this;
					var runGenericIn = function() {
						startFrame = mc.getFrameLabelForName("*_to_" + state2);
						if (startFrame) {
							endFrame = mc.getFrameLabelForName("end_*_to_" + state2);

							if (!endFrame) {
								endFrame = mc.getFrameLabelForName(state2);
							}
							if (endFrame) {
								mc.playFromFrameToFrame(startFrame.frame, endFrame.frame, callbackWhenDone);
								return;
							} else {
								if (callbackWhenDone != null) {
									callbackWhenDone();
								}
							}
						} else {
							if (callbackWhenDone != null) {
								callbackWhenDone();
							}
						}
					}
					startFrame = this.getFrameLabelForName(state1 + "_to_*");
					if (startFrame) {
						endFrame = this.getFrameLabelForName("end_" + state1 + "_to_*");
						if (endFrame) {
							this.playFromFrameToFrame(startFrame.frame, endFrame.frame, runGenericIn);
							return;
						} else {
							runGenericIn();
						}
					} else {
						runGenericIn()
					}
				}
			},
			playFromLabelToLabel: function(l1:*, l2:*, callbackWhenDone:Function=null) {
				if (l1 is String) {
					l1 = this.getFrameLabelForName(l1);
				}
				if (l2 is String) {
					l2 = this.getFrameLabelForName(l2);
				}
				this.playFromFrameToFrame(l1.frame, l2.frame, callbackWhenDone);
			},
			playFromFrameToFrame: function(f1:int, f2:int, callbackWhenDone:Function=null) {
				if (f1 == f2) {
					if (callbackWhenDone != null) {
						callbackWhenDone();
					}
					this.gotoAndStop(f2)
					return;
				}
				if (!this.isPlayheadBetweenFrames(f1, f2)) {
					this.gotoAndStop(f1);
				} 
				this.playFromFrameToFrameTarget = f2;
				this.playFromFrameToFrameCallback = callbackWhenDone;
				if (f1 > f2) {
					this.playFromFrameToFrameIncrement = -1;
				} else {
					this.playFromFrameToFrameIncrement = 1;
				}
				var mc:MovieClip = this;
				var myTarget:int = f2;
				var playFromFrameToFrameHandler:Function = function() {
					if (mc.currentFrame == mc.playFromFrameToFrameTarget) {
						mc.removeEventListener(Event.ENTER_FRAME, playFromFrameToFrameHandler);
						if (mc.playFromFrameToFrameCallback != null) {
							mc.playFromFrameToFrameCallback();
						}
					} else {
						// in the case of a dynamically retargeted animation there
						// could be multiple handlers working so we need to check
						// if the frame we registered for is still the frame being targeted
						// if this isn't done the animation will play faster because
						// the multiple frame handlers will process the frame
						if (mc.playFromFrameToFrameTarget == myTarget) {
							mc.gotoAndStop(mc.currentFrame + mc.playFromFrameToFrameIncrement);
						}
					}
				}
				this.addEventListener(Event.ENTER_FRAME, playFromFrameToFrameHandler);
			},
			isPlayheadBetweenLabels: function(l1:*, l2:*, inclusive:Boolean=false) {
				return this.isFrameBetweenLabels(this.currentFrame, l1, l2, inclusive)
			},
			isPlayheadBetweenFrames: function(f1:int, f2:int, inclusive:Boolean=false) {
				if (inclusive) {
					return (this.currentFrame >= f1 && this.currentFrame <= f2 ||
							this.currentFrame >= f2 && this.currentFrame <= f1);
				} else {
					return (this.currentFrame > f1 && this.currentFrame < f2 ||
							this.currentFrame > f2 && this.currentFrame < f1)
				}
			},
			isFrameBetweenLabels: function(frame:int, l1:*, l2:*, inclusive:Boolean=false) {
				if (l1 == null || l2 == null) {
					return false;
				}				
				if (l1 is String) {
					l1 = this.getFrameLabelForName(l1);
				}
				if (l2 is String) {
					l2 = this.getFrameLabelForName(l2);
				}
				if (inclusive) {
					return (frame >= l1.frame && frame <= l2.frame ||
							frame >= l2.frame && frame <= l1.frame);
				} else {
					return (frame > l1.frame && frame < l2.frame ||
							frame > l2.frame && frame < l1.frame)
				}
			},
			areLabelsAdjacent: function(l1:*, l2:*) {
				if (l1 == null || l2 == null) {
					return false;
				}
				if (l1 is FrameLabel) {
					l1 = l1.name;
				}
				if (l2 is FrameLabel) {
					l2 = l2.name;
				}
				for (var i:int=0; i < this.currentLabels.length - 1; i++) {
					if (this.currentLabels[i].name == l1) {
						return this.currentLabels[i+1].name == l2
							|| this.currentLabels[i-1].name == l2;
					}
				}
				return false;
			},
			isLabel2ImmediatelyAfterLabel1: function(l1:*, l2:*) {
				if (l1 == null || l2 == null) {
					return false;
				}
				if (l1 is FrameLabel) {
					l1 = l1.name;
				}
				if (l2 is FrameLabel) {
					l2 = l2.name;
				}
				for (var i:int=0; i < this.currentLabels.length - 1; i++) {
					if (this.currentLabels[i].name == l1) {
						return this.currentLabels[i+1].name == l2;
					}
				}
				return false;
			},
			getCurrentFrameLabel: function() {
				return this.getFrameLabelForName[this.currentLabel];
			},
			getFrameLabelForName: function(name:String) {
				for each (var l:FrameLabel in this.currentLabels) {
					if (l.name == name) {
						return l;
					}
				}
				return null;
			}
		};
	}
}